/*            Copyright(c) 2006-2008 Dave Sexton           *
 * http://www.codeplex.com/DocProject                      *
 * http://www.codeplex.com/DocProject/Project/License.aspx *
 ***********************************************************/
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Drawing;
using System.Data;
using System.Text;
using System.Windows.Forms;

namespace DaveSexton.DocProject.Sandcastle.TopicManagement
{
	internal sealed partial class TopicEditorControl : UserControl
	{
		#region Public Properties
		[Browsable(false), DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
		public TopicManager Manager
		{
			get
			{
				return manager;
			}
			set
			{
				if (manager == value)
					return;

				UpdateManagerEventHandlers(false);

				manager = value;

				UpdateManagerEventHandlers(true);

				if (topic != null)
				{
					EnsureChangesCommitted();
					
					topic = null;

					SynchronizeCommentsEditor();
				}

				if (manager != null)
				{
					editor.Importer = manager.Importer;
					editor.DisplayManager = manager.DisplayManager;
					editor.Enabled = true;
				}
				else
					editor.Enabled = false;
			}
		}

		[Browsable(false), DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
		public ITopicHierarchy Hierarchy
		{
			set
			{
				hierarchy = value;
			}
		}

		[Browsable(false), DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
		public Topic Topic
		{
			set
			{
				topic = value;

				SynchronizeCommentsEditor();
			}
		}

		[Category("Appearance")]
		public int ListWidth
		{
			get
			{
				return editor.ListWidth;
			}
			set
			{
				editor.ListWidth = value;
			}
		}
		#endregion

		#region Private / Protected
		private Topic topic;
		private TopicManager manager;
		private ITopicHierarchy hierarchy;
		#endregion

		#region Constructors
		/// <summary>
		/// Constructs a new instance of the <see cref="TopicEditorControl" /> class.
		/// </summary>
		public TopicEditorControl()
		{
			InitializeComponent();

			editor.DefaultDocumentName = ContentManagement.XmlCommentSectionsDocument.VirtualCommentsDocumentName;
		}
		#endregion

		#region Methods
		private void UpdateManagerEventHandlers(bool bind)
		{
			if (manager != null)
			{
				if (bind)
				{
					manager.Saving += manager_Saving;
					manager.Saved += manager_Saved;
				}
				else
				{
					manager.Saving -= manager_Saving;
					manager.Saved -= manager_Saved;
				}
			}
		}

		/// <remarks>
		/// A bug exists in either VS or the .NET framework that causes the TreeView.AfterSelect event to be raised without it getting the input focus first, 
		/// but only if it was clicked while a TextBox currently has the input focus; i.e., while editing a topic's XML comments in source mode, if you click 
		/// the topic explorer's tree view then your changes will be lost since AfterSelect will be raised immediately, causing the content editor's topic to be 
		/// changed to the newly selected topic, and then afterwards the source textbox's Validating event will be raised, once it finally loses focus,  
		/// for the changes you have made to the previously selected topic.  This is verifyable by examining the order of events in the Debug output.
		/// The order of events is normally the other way around, although I couldn't reproduce this issue in a smaller test app and it only appears to happen
		/// when the topic editor and topic explorer controls are being hosted by different tool windows (e.g., the Topic Management dialog actually behaves
		/// correctly and it hosts both controls in the same Form.)
		/// <para>
		/// Apparently a related bug was reported in the .NET 1.1 Framework (http://support.microsoft.com/kb/824996) and was fixed in a service pack.
		/// </para>
		/// The solution is to call <see cref="ValidateChildren"/> explicitly each time a new topic is assigned since the Framework may not call it on its own.
		/// </remarks>
		private void EnsureChangesCommitted()
		{
			editor.ValidateChildren();
		}

		private void SynchronizeCommentsEditor()
		{
			EnsureChangesCommitted();

			bool enabled;

			if (enabled = UpdateCommentsEditorState())
			{
				ContentManagement.ContentItem currentItem = editor.SelectedItem;

				if (currentItem != null)
					editor.DefaultItemName = currentItem.Name;

				while (!editor.IsDesignerReady)
					Application.DoEvents();
				
				editor.Document = manager.GetContentDocument(topic);
			}
			else
				editor.Document = null;

			editor.Enabled = enabled;
		}

		private bool UpdateCommentsEditorState()
		{
			bool enable = false;

			if (manager != null)
			{
				TopicType type = (topic == null) ? TopicType.General : topic.TopicType;
				enable = type != TopicType.General && type != TopicType.Module
					&& type != TopicType.Container && type != TopicType.Conceptual && type != TopicType.Html;
			}

			return enable;
		}

		/// <summary> 
		/// Clean up any resources being used.
		/// </summary>
		/// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
		protected override void Dispose(bool disposing)
		{
			if (disposing)
			{
				UpdateManagerEventHandlers(false);

				if (components != null)
					components.Dispose();
			}
			base.Dispose(disposing);
		}
		#endregion

		#region Events
		private readonly object ListWidthChangedEvent = new object();

		/// <summary>
		/// Event raised when the value of the <see cref="ListWidth"/> property changes.
		/// </summary>
		[Category("Property Changed")]
		[Description("Event raised when the value of the ListWidth property changes.")]
		public event EventHandler ListWidthChanged
		{
			add
			{
				lock (ListWidthChangedEvent)
				{
					Events.AddHandler(ListWidthChangedEvent, value);
				}
			}
			remove
			{
				lock (ListWidthChangedEvent)
				{
					Events.RemoveHandler(ListWidthChangedEvent, value);
				}
			}
		}

		/// <summary>
		/// Raises the <see cref="ListWidthChanged" /> event.
		/// </summary>
		/// <param name="e"><see cref="EventArgs" /> object that provides the arguments for the event.</param>
		private void OnListWidthChanged(EventArgs e)
		{
			EventHandler handler = null;

			lock (ListWidthChangedEvent)
			{
				handler = (EventHandler) Events[ListWidthChangedEvent];
			}

			if (handler != null)
				handler(this, e);
		}
		#endregion

		#region Event Handlers
		private void manager_Saving(object sender, TopicManagerChangesEventArgs e)
		{
			// When the topic editor is hosted in a tool window, changing the focus 
			// to the IDE does not cause the editor to lose focus or validate.
			// To ensure that all changes are known before the manager attempts to save,
			// validation must occur though since action is taken via a validating event
			// handler.
			EnsureChangesCommitted();
		}

		private void manager_Saved(object sender, TopicManagerChangesEventArgs e)
		{
			if ((e.Changes & TopicManagerChanges.Documentation) == TopicManagerChanges.Documentation)
			{
				// ensure that bolded items (dirty state) are reset (normal state) after being saved, 
				// without requiring the user to manually focus the editor control by clicking it.
				editor.Refresh();
			}
		}

		private void editor_DesignerReady(object sender, EventArgs e)
		{
			editor.Enabled = UpdateCommentsEditorState();
		}

		private void editor_DesignerDragDrop(object sender, DaveSexton.Controls.HtmlEditorDragDropEventArgs e)
		{
			string nodeId = e.DropValue;

			if (!string.IsNullOrEmpty(nodeId))
			{
				TopicNode node = hierarchy.GetNodeById(nodeId);

				if (node != null)
				{
					HtmlElement replacement;

					if (topic.TopicType == TopicType.Conceptual)
					{
						if (node.Topic.TopicType == TopicType.Conceptual)
						{
							replacement = e.Document.CreateElement("doc:link");
							replacement.SetAttribute("xlink:href", node.Topic.Id);
							replacement.InnerText = node.Topic.TocTitle;
						}
						else if (node.Topic.TopicType == TopicType.Html)
						{
							replacement = e.Document.CreateElement("doc:externalLink");
							replacement.AppendChild(e.Document.CreateElement("doc:linkText")).InnerText = node.Topic.TocTitle;
							replacement.AppendChild(e.Document.CreateElement("doc:linkUri")).InnerText = System.IO.Path.GetFileName(node.Topic.File);
							replacement.AppendChild(e.Document.CreateElement("doc:linkTarget")).InnerText = "_self";
						}
						else
						{
							replacement = e.Document.CreateElement("doc:codeEntityReference");
							replacement.InnerText = node.Topic.Id;
						}
					}
					else
					{
						if (node.Topic.TopicType == TopicType.Conceptual)
						{
							replacement = e.Document.CreateElement("a");
							replacement.SetAttribute("href", node.Topic.Id + ".htm");
							replacement.InnerText = node.Topic.TocTitle;
						}
						else if (node.Topic.TopicType == TopicType.Html || topic.TopicType == TopicType.Html)
						{
							replacement = e.Document.CreateElement("a");
							replacement.SetAttribute("href", System.IO.Path.GetFileName(node.Topic.File));
							replacement.InnerText = node.Topic.TocTitle;
						}
						else
						{
							replacement = e.Document.CreateElement("doc:see");
							replacement.SetAttribute("cref", node.Topic.Id);
							replacement.InnerText = node.Topic.TocTitle;
						}
					}

					e.Replacement = replacement;

					hierarchy.NotifyDroppedNodeInEditor();
				}
			}
		}

		private void editor_SourceDragDrop(object sender, DragEventArgs e)
		{
			string nodeId = e.Data.GetData("Text") as string;

			if (!string.IsNullOrEmpty(nodeId))
			{
				TopicNode node = hierarchy.GetNodeById(nodeId);

				if (node != null)
				{
					if (topic.TopicType == TopicType.Conceptual)
						editor.PasteSourceText(hierarchy.CreateConceptualLink(node));
					else
						editor.PasteSourceText(hierarchy.CreateReferenceLink(node));

					hierarchy.NotifyDroppedNodeInEditor();
				}
			}
		}

		private void editor_SourceDragOver(object sender, DragEventArgs e)
		{
			e.Effect = DragDropEffects.Copy;
		}

		private void editor_SourceDragEnter(object sender, DragEventArgs e)
		{
			e.Effect = DragDropEffects.Copy;
		}

		private void editor_ListWidthChanged(object sender, EventArgs e)
		{
			OnListWidthChanged(e);
		}
		#endregion
	}
}
